/// <reference types="vite/client" />
import type { MaybeUndefine } from "../types.js";
import { isArrayBuffer } from "../util.js";

interface IHandshake {
    readonly major: number;
    readonly minor: number;
    readonly patch: number;
    readonly build: number;
}

const CLIENT_VERSION = [0, 0, 0, 1] as const;

function parseHandshake(buffer: Uint8Array): IHandshake {
    if (buffer.length !== 4) {
        throw new Error("250125224224267, invalid handshake");
    }
    const major = buffer[0]!;
    const minor = buffer[1]!;
    const patch = buffer[2]!;
    const build = buffer[3]!;
    return {
        major,
        minor,
        patch,
        build,
    };
}

interface IPromiseInfo<T> {
    readonly promise: Promise<T>;
    readonly resolve: (value: T) => void;
    readonly reject: (reason: unknown) => void;
}

function promise<T>(): IPromiseInfo<T> {
    let resolve: (value: T) => void;
    let reject: (reason: unknown) => void;
    const p = new Promise<T>((res, rej) => {
        resolve = res;
        reject = rej;
    });
    return { promise: p, resolve: resolve!, reject: reject! };
}

export interface Ws {
    addEventListener(
        type: "open",
        callback: (this: this, ev: Event) => void,
        options?: AddEventListenerOptions | boolean
    ): void;

    addEventListener(
        type: "close",
        callback: (this: this, ev: Event) => void,
        options?: AddEventListenerOptions | boolean
    ): void;

    addEventListener(
        type: "message",
        callback: (this: this, ev: CustomEvent<Uint8Array>) => void,
        options?: AddEventListenerOptions | boolean
    ): void;

    addEventListener(
        type: string,
        callback: EventListenerOrEventListenerObject | null,
        options?: AddEventListenerOptions | boolean
    ): void;
}

export class Ws extends EventTarget {
    /** WebSocket 地址 */
    readonly url: string;

    /** 是否正在建立连接中 */
    #connecting: IPromiseInfo<boolean> | undefined;

    /** WebSocket 实例 */
    #ws: WebSocket | undefined;

    constructor(url: string) {
        super();
        this.url = url;
    }

    /**
     * 是否已连接
     */
    get connected() {
        return this.#ws !== undefined;
    }

    connect(): Promise<boolean> {
        if (this.#ws !== undefined) {
            return Promise.resolve(true);
        }
        if (this.#connecting !== undefined) {
            return this.#connecting.promise;
        }

        this.#connecting = promise();
        console.log("开始进行连接...");
        const ws = new WebSocket(this.url);
        ws.binaryType = "arraybuffer";

        ws.onerror = (event) => {
            console.log("连接出错", event);
            this.#connecting!.resolve(false);
            this.#connecting = undefined;
        };

        ws.onopen = () => {
            ws.onerror = null;
            this.#handshake(ws);
        };
        return this.#connecting.promise;
    }

    #handshake(ws: WebSocket) {
        ws.onclose = () => {
            if (this.#connecting === undefined) {
                throw new Error("250125224224267, 未知错误");
            }
            this.#connecting.reject(new Error("连接意外关闭"));
            this.#connecting = undefined;
        };
        ws.onerror = (event) => {
            if (this.#connecting === undefined) {
                throw new Error("250125224224267, 未知错误");
            }
            this.#connecting.reject(event);
            this.#connecting = undefined;
        };
        ws.onmessage = (event) => {
            ws.onclose = null;
            ws.onerror = null;
            ws.onmessage = null;
            if (this.#connecting === undefined) {
                throw new Error("250125224224267, 未知错误");
            }

            if (!isArrayBuffer(event.data)) {
                ws.close();
                this.#connecting.reject(new Error("非法数据包"));
                this.#connecting = undefined;
                return;
            }

            const buffer = new Uint8Array(event.data);
            try {
                const handshake = parseHandshake(buffer);
                console.log("握手成功", handshake);
                ws.onmessage = this.#messageHandler;
                ws.onclose = this.#closeHandler;
                this.#ws = ws;
                this.#connecting.resolve(true);
                this.#connecting = undefined;
                this.dispatchEvent(new Event("open"));
            } catch (e) {
                if (this.#connecting === undefined) {
                    throw new Error("250125224224267, 未知错误");
                }
                console.error(e);
                ws.close();
                this.#connecting.reject(e);
                this.#connecting = undefined;
            }
        };
        ws.send(new Uint8Array([...CLIENT_VERSION]));
        console.log("发送握手包");
    }

    #closeHandler = () => {
        this.#ws = undefined;
        console.log("%c连接已关闭", "color: red");
        this.dispatchEvent(new Event("close"));
    };

    #messageHandler = (event: MessageEvent<unknown>) => {
        if (!isArrayBuffer(event.data)) {
            console.log("非法数据包", event);
            return;
        }
        const buffer = new Uint8Array(event.data);
        this.dispatchEvent(new CustomEvent("message", { detail: buffer }));
        // if (this.onmessage !== undefined) {
        //     return this.onmessage(buffer);
        // }
        // console.log("message: ", bufferToString(buffer));
    };

    onmessage: MaybeUndefine<(data: Uint8Array) => void>;

    send(data: Uint8Array) {
        if (this.#ws === undefined) {
            console.warn("WebSocket 未连接");
            return;
        }
        if (data.length === 0) {
            console.warn("取消发送空数据包");
            return;
        }
        let str = "";
        for (const v of data) {
            str += v.toString(16).padStart(2, "0") + " ";
        }
        console.log("发送数据: ", str);
        this.#ws.send(data);
    }
}

export function createWs() {
    let focus = true;
    let url = `ws://${location.hostname}:8849`;
    // if (import.meta.env.DEV) {
    //     url = "/ws";
    // }
    const ws = new Ws(url);
    ws.connect();
    window.addEventListener("focus", () => {
        focus = true;
        ws.connect();
    });
    window.addEventListener("blur", () => {
        focus = false;
    });
    ws.addEventListener("close", () => {
        async function reconnect() {
            while (focus) {
                if ((await ws.connect()) === true) {
                    console.log("连接已恢复");
                    break;
                }
            }
        }
        reconnect();
    });
    return ws;
}
